﻿#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
int main()
{
	//一维数组练习：
	int a[] = { 1,2,3,4 };
	printf("%zd\n", sizeof(a));//整个数组的大小，答案是：16
	printf("%zd\n", sizeof(a + 0));//首元素的地址，答案是：4/8
	printf("%zd\n", sizeof(*a));//解引用首元素地址，就是首元素，答案是：4
	printf("%zd\n", sizeof(a + 1));//首元素地址加1，得到的是第二个元素地址，答案是：4/8
	printf("%zd\n", sizeof(a[1]));//第二个元素，答案是：4
	printf("%zd\n", sizeof(&a));//对整个数组取地址，取出的是整个数组的地址，但也是地址，答案是：4/8
	printf("%zd\n", sizeof(*&a));//对数组取地址再解应用，还是整个数组，答案是：16
	printf("%zd\n", sizeof(&a + 1));//取出整个数组地址后再加1，调过整个数组，指向数组后边的地址，还是地址，答案是：4/8
	printf("%zd\n", sizeof(&a[0]));//对下标为0的元素取地址，就是取1的地址，首元素的地址，答案是：4/8
	printf("%zd\n", sizeof(&a[0] + 1));//首元素地址加1，得到的是第二个元素的地址，2的地址，答案是：4/8



	//字符数组练习：


	/*
	数组名是首元素的地址，有两个例外：
	1.单独放入sizeof中->	整个数组的大小，单位是字节
	2.&arr->				整个数组的地址，运算跳过整个数组
	*/
	char arr[] = { 'a','b','c','d','e','f' };
	printf("%zd\n", sizeof(arr));//arr整个放入sizeof中是计算整个数组的大小，单位是字节，答案是：6
	printf("%zd\n", sizeof(arr + 0));//arr+0其实就是首元素地址加零,也就是首元素地址，答案是：4/8
	printf("%zd\n", sizeof(*arr));//对首元素地址解应用，得到的就是首元素，答案是：1
	printf("%zd\n", sizeof(arr[1]));//经典的取出下标为1的元素，答案是：1
	printf("%zd\n", sizeof(&arr));//对arr整个数组取地址，但也是地址，答案是：4/8
	printf("%zd\n", sizeof(&arr + 1));//对arr整个数组取地址再加1，得到的就是跳过arr数组后的位置，但也是地址，答案是：4/8
	printf("%zd\n", sizeof(&arr[0] + 1));//对下标为0的元素取地址在加1，就是第二个元素的地址，也是地址，答案是：4/8
	/*
	使用srtlen需要包含头文件->	#include<string.h>
	strlen的参数是指针类型（const char* str），它的参数是一个地址，如果强行传入错误的类型，会造成非法访问
	*/
	char arr[] = { 'a','b','c','d','e','f' };
	printf("%zd\n", strlen(arr));//strlen计算的是字符串，arr是首元素的地址，没有"/0"，答案是：随机值
	printf("%zd\n", strlen(arr + 0));//答案是：随机值
	printf("%zd\n", strlen(*arr));//对首元素地址解应用，得到的是'a',strlen的参数是指针类型（const char* str）,传入参数错误，不能当做地址传入。答案是：非法访问
	printf("%zd\n", strlen(arr[1]));//答案是：非法访问
	printf("%zd\n", strlen(&arr));//取出整个数组地址，但没有"/0"，答案是：随机值
	printf("%zd\n", strlen(&arr + 1));//跳过arr这个数组的地址，答案是：随机值
	printf("%zd\n", strlen(&arr[0] + 1));//第二个元素的地址，答案是：随机值

	char arr[] = "abcdef";
	/*
	"abcdef"->内部元素为'a','b','c','d','e','f','/0',
	*/
	printf("%zd\n", sizeof(arr));//整个数组的大小，为7个元素，答案是：7
	printf("%zd\n", sizeof(arr + 0));//首元素地址，答案是：4/8
	printf("%zd\n", sizeof(*arr));//对首元素地址解应用，就是'a'，答案是：1
	printf("%zd\n", sizeof(arr[1]));//就是第二个元素，也就是'b'，答案是：1
	printf("%zd\n", sizeof(&arr));//取出的是整个数组的地址，但也是地址，答案是：4/8
	printf("%zd\n", sizeof(&arr + 1));//对arr整个数组取地址再加1，得到的就是跳过arr数组后的地址，但也是地址，答案是：4/8
	printf("%zd\n", sizeof(&arr[0] + 1));//对下标为0的元素取地址在加1，就是第二个元素'b'的地址，也是地址，答案是：4/8

	char arr[] = "abcdef";
	/*
	strlen计算的是'/0'前字符的个数，不包括'/0'
	*/
	printf("%zd\n", strlen(arr));//因为最后隐含'/0'，答案是：6
	printf("%zd\n", strlen(arr + 0));//传入的参数为首元素地址，和上一个一样，答案是：6
	printf("%zd\n", strlen(*arr));//取出首元素，元素为字符'a'，与需要传入参数不符，不能当做数组传入，答案是：非法访问
	printf("%zd\n", strlen(arr[1]));//与上一题一样，只不过换成了'b'，答案是：非法访问
	printf("%zd\n", strlen(&arr));//取出的是整个数组的地址，但指向和起始位置，传入的还是首元素地址，strlen会转换为char*，所以，答案是：6
	printf("%zd\n", strlen(&arr + 1));//指向了arr数组后的地址，没有明确的'/0'，答案是：随机值
	printf("%zd\n", strlen(&arr[0] + 1));//第一个元素地址加1，得到的是第二个元素的地址，答案是：5

	char* p = "abcdef";
	/*
	这里为常量字符串，存入的地址为首元素地址，也就是'a'的地址放入p中，它指向'a'
	*/
	printf("%zd\n", sizeof(p));//p是'a'的地址，答案是：4/8
	printf("%zd\n", sizeof(p + 1));//p+1是'b'的地址，答案是：4/8
	printf("%zd\n", sizeof(*p));//对p解应用，就是'a'，答案是：1
	printf("%zd\n", sizeof(p[0]));//可以理解为*（p+0）,就是'a'，答案是：1
	printf("%zd\n", sizeof(&p));//对p取地址，还是地址，答案是：4/8
	printf("%zd\n", sizeof(&p + 1));//二级指针，'a'地址的地址，指向p地址后边的地址，答案是：4/8
	printf("%zd\n", sizeof(&p[0] + 1));//对'a'取地址，再加1，得到的是'b'的地址，答案是：4/8

	char* p = "abcdef";
	printf("%zd\n", strlen(p));//strlen接收的参数就是char*类型，也就是首元素地址p，答案是：6
	printf("%zd\n", strlen(p + 1));//p+1就是第二个元素地址，也就是'b'的地址，答案是：5
	printf("%zd\n", strlen(*p));//对地址p解应用，得到的就是'a'，传入会造成错误，答案是：非法访问
	printf("%zd\n", strlen(p[0]));//就是'a'，传入造成错误，答案是：非法访问
	printf("%zd\n", strlen(&p));//二级指针，对'a'的地址取地址，后边的元素不可控，答案是：随机值
	printf("%zd\n", strlen(&p + 1));//跳过p地址的地址，元素不可控，答案是：随机值
	printf("%zd\n", strlen(&p[0] + 1));//对'a'取地址加1，得到的就是'b'的地址，答案是：5

	//二维数组练习：
	/*
	* 二维数组也是数组，也满足数组的基本要求：
	1. sizeof(数组名)，这⾥的数组名表⽰整个数组，计算的是整个数组的⼤⼩。
	2. &数组名，这⾥的数组名表⽰整个数组，取出的是整个数组的地址。
	3. 除此之外所有的数组名都表⽰⾸元素的地址。
	*/
	int a[3][4] = { 0 };//实际为int a[3][4]={{0,0,0,0}{0,0,0,0}{0,0,0,0}};
	printf("%zd\n", sizeof(a));//整个数组大小，答案是：48
	printf("%zd\n", sizeof(a[0][0]));//第一个元素组的第一个元素，答案是：4
	printf("%zd\n", sizeof(a[0]));//第一个元素组的大小，就是{0,0,0,0}，答案是：16
	printf("%zd\n", sizeof(a[0] + 1));//第一个元素组第二个元素的地址，就是a[0][1]的地址,答案是：4/8
	printf("%zd\n", sizeof(*(a[0] + 1)));//对第一个元素组第二个元素解应用，就是第二个元素，答案是：4
	printf("%zd\n", sizeof(a + 1));//没有单独存放，没有取地址，a就是数组首元素的地址a[0]->int(*)[4]，就是第一个元素组的地址，加1，就是第二个元素组的地址，答案是：4/8
	printf("%zd\n", sizeof(*(a + 1)));//对第二个元素组地址解应用，就是第二个元素组，等价于a[1],相当于单独放在了sizeof里面，答案是：16
	printf("%zd\n", sizeof(a[1]));//第二个元素组的地址，单独存放，就是第二个元素组的大小，单位是字节，答案是：16
	printf("%zd\n", sizeof(&a[0] + 1));//对第一个元素组取地址，得到的就是第一个元素组的地址，加1就是第二个元素组的地址，答案是：4/8
	printf("%zd\n", sizeof(*(&a[0] + 1)));//第二个元素组解应用，就是一个一维数组，单独存放在sizeof内，就是整个第二元素组的大小，答案是：16
	printf("%zd\n", sizeof(*a));//a是首元素地址，就是第一元素组地址，解应用得到的整个第一元素组地址，答案是：16
	printf("%zd\n", sizeof(a[3]));//等价于*(a+3)，在sizeof内，与a[0]或者a[1]等一致，答案是：16


	//指针运算笔试题：
	//题目1：
	//int main()
	//{
		int a[5] = { 1, 2, 3, 4, 5 };
		int* ptr = (int*)(&a + 1);//&a取出的是整个数组的地址（类型为int(*)[5]），加1跳到数组a后边的位置；强制类型转换为int*类型指针后传入ptr中,从之前运算动20个字节强制变为运算动4个字节
		printf("%d , %d", *(a + 1), *(ptr - 1));//*(a+1)是首元素地址加1后解应用，就是2；     ptr-1得到的应该是a[5]的地址，*(ptr-1)解应用得到的是5
	//	return 0;
	//}

	//题目2：

	return 0;
}
//在X86环境下
//假设结构体的⼤⼩是20个字节
//程序输出的结构是啥？
//%p打印指针，回将地址完全打印出来，不会省略前面的0
struct Test
{
	int Num;
	char* pcName;
	short sDate;
	char cha[2];
	short sBa[4];
}*p = (struct Test*)0x100000;//将0x100000，强制类型转换为一个结构体指针类型，根据题目，大小为20个字节，也就是p运算时会移动20个字节大小
int main()
{
	printf("%p\n", p + 0x1);//p+1就是结构后的位置，20个字节，十六进制0x14，答案是：100014
	printf("%p\n", (unsigned long)p + 0x1);//强制类型转换为长整型了，答案是：100001
	printf("%p\n", (unsigned int*)p + 0x1);//unsigned int*运算跳4个字节，十六进制为0x4，答案是：100004
	return 0;
}


//题目3：
/*
考察了对逗号表达是的理解和观察，忽略括号和逗号表达式，容易出现身体错误
*/
#include <stdio.h>
int main()
{
	int a[3][2] = { (0, 1), (2, 3), (4, 5) };//逗号表达式，从左向右计算，整个表达式结果为最后侧的结果，该数组实际为：int a[3][2]={{1,3},{5,0},{0,0}};
	int* p;
	p = a[0];//p取出的是第一个元素组的地址，类型为int*，地址指向为第一个元素处的地址，也就是1的地址
	printf("%d", p[0]);//p[0]等价于*(p+0),p+0就是1的地址，解应用后就是1
	return 0;
}

//题目4：
//假设环境是x86环境，程序输出的结果是啥？
#include <stdio.h>
int main()
{
	int a[5][5];//a是首元素地址，类型为int(*)[5]，运算每次跳过5个整型元素
	int(*p)[4];//p的类型为int(*)[4]，运算每次跳过4个整型元素
	p = a;
	printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
	//指针减指针，得到的是指针间元素的个数。两者相减，元素个数为-4，内存中存在的是补码，但是通过不用占位符表达出来的不一样。
	// %p是地址，地址不存在补码概念。&d会打印其原码。
	return 0;
}

//题目5：
#include <stdio.h>
int main()
{
	int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };//此数组为int aa[2][5]={{1,2,3,4,5},{6,7,8,9,10}};
	int* ptr1 = (int*)(&aa + 1);//aa取地址，取出的是整个数组的地址，大小为40个字节，加1就跳过了整个数组，指针位置指向了数组后的地址
	int* ptr2 = (int*)(*(aa + 1));//aa是首元素地址，aa+1相当于aa[1],加1就跳到了第二个元素组的首元素位置，解应用就是第二个元素组首元素地址，就是6的位置
	printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));//*(ptr1-1)为10，*(ptr2-1)为5
	return 0;
}

//题目6：
/*
考验字符串在字符指针中的存放形式，以及对二级指针的理解
*/
#include <stdio.h>
int main()
{
	char* a[] = { "work","at","alibaba" };//字符指针数组，字符串指针里存的都是每个字符首元素地址：&w，&a，&a。类型都是char*
	char** pa = a;//a是字符指针数组的首元素地址，也就是指向w的地址，pa则是指向w指针的指针
	pa++;//pa++会跳过第一个字符串地址，因为在a中，&w/&a/&a是连续的，指向第二个"at"的首元素地址
	printf("%s\n", *pa);//这里解应用得到的就是a的地址，使用%s打印，就会打印当前地址开始的字符串，也就是"at"
	return 0;
}

//题目7：
#include <stdio.h>
int main()
{
	char* c[] = { "ENTER","NEW","POINT","FIRST" };//这里实际存放的是每个字符串首元素的地址：{    &E,&N,&P,&F   };类型为：char*
	char** cp[] = { c + 3,c + 2,c + 1,c };//c是E的地址，c+3得到的是F的地址；依次为P的地址，N的地址，E的地址；cp存放的就是{   &&F,&&P,&&N,&&E  }；类型为：char**
	char*** cpp = cp;//cp为首元素地址，就是&&&F，指向cpp；类型为char***
	//三级指针加减1，操作的是二级指针数组中的位置，二级指针加减，操作的是在一级指针中元素的位置
	printf("%s\n", **++cpp);//++cpp,先自加，再使用；cpp自加1，就指向了&&&P，**(&&&p)就是&P，以%s形式打印，就是"POINT"；
	printf("%s\n", *-- * ++cpp + 3);
	//目前cpp位置为&&&P，先执行++cpp，则又指向了&&&N的位置，解应用为&&N，再进行自减1，就相当于在c中进行操作，&&P的位置自减1，得到的就是&&E的位置；解应用得到的是&E；&E+3，跳过三个字符，得到的是："ER"；
	printf("%s\n", *cpp[-2] + 3);
	//目前cpp指向的位置为&&&N，*cpp[-2]等价于**(cpp-2)，cpp位置减2，就又回到了&&&F的位置，但是cpp未发生变化，这里需要注意；解应用两次**(&&&F)，得到&F的地址,+3得到S的地址，打印出来为，"ST"
	printf("%s\n", cpp[-1][-1] + 1);
	//目前cpp指向为&&&N，cpp[-1][-1]等价于*(*(cpp-1)-1):逐步分析为cpp-1，指向&&&P，解应用为&&P，&&P减1则是变为&N，&N再加1，则是E的地址，打印出来则是"EW"
	return 0;
}